﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Markup;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using Microsoft.Win32;
using System.Reflection;

namespace SubtitleFontCollector
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        private const string LAST_LOCATION = "LastFileOpenLocaltion";
        private const string V4FONTPREFIX = "* ";

        /// <summary>
        /// all avaialable fonts
        /// </summary>
        private FontFamily[] allFonts;

        /// <summary>
        /// opening files.
        /// </summary>
        private Subtitle[] files;

        /// <summary>
        /// to improve performance, store the converted character in the hash table.
        /// </summary>
        private Hashtable chineseCharacterhash;

        public MainWindow()
        {
            this.chineseCharacterhash = new Hashtable();
            InitializeComponent();
            this.txtMessage.Document.Blocks.Clear();

            this.Title = string.Format(".ass subtitle font checker -- www.1Few.com {0}", System.Reflection.Assembly.GetExecutingAssembly().GetName().Version);

            this.AllowDrop = true;
            this.Drop += new DragEventHandler(this.DropFiles);
        }

        public FontFamily[] AllFonts
        {
            get
            {
                if (this.allFonts == null)
                {
                    var families = Fonts.SystemFontFamilies;
                    this.allFonts = families.ToArray();
                }

                return this.allFonts;
            }
        }

        private void BrowserSubtitleFile(object sender, RoutedEventArgs e)
        {
            OpenFileDialog filedialog = new OpenFileDialog();
            filedialog.Title = "Choose subtitle files";
            filedialog.Filter = "Advanced SubStation Alpha|*.ass|All Files|*.*";
            filedialog.FilterIndex = 0;
            string lastLocation = this.GetKey(LAST_LOCATION);
            filedialog.Multiselect = true;
            if (!string.IsNullOrWhiteSpace(lastLocation) && System.IO.Directory.Exists(lastLocation))
            {
                filedialog.InitialDirectory = lastLocation;
            }

            if (true == filedialog.ShowDialog(this))
            {
                // save location to registration.
                string newPath = System.IO.Path.GetDirectoryName(filedialog.FileName);
                this.SetKey(LAST_LOCATION, newPath);

                // set the text to text box.
                this.ParsingFiles(filedialog.FileNames);
            }
            else
            {
                this.files = null;
            }
        }

        private void ParsingFiles(string[] files)
        {
            this.files = Array.ConvertAll<string, Subtitle>(files, (s) => { return new Subtitle(s, SubtitleType.ASS); });

            if (this.files.Length == 1)
            {
                this.txtSubtitleFile.Text = this.files[0].Filename;
            }
            else
            {
                this.txtSubtitleFile.Text = "multiple files";
            }
        }

        private void ParseSubtitleFile(object sender, RoutedEventArgs e)
        {
            if (this.files == null || this.files.Length == 0)
            {
                this.AppendMessage("Please select the subtitle files.", Colors.Red);
                return;
            }

            this.btnParse.IsEnabled = false;
            Task parseTask = Task.Factory.StartNew((Action)(() =>
             {
                 foreach (var file in this.files)
                 {
                     this.ParseSingleFile(file);
                 }

                 this.AppendContactInfo();
             }));

            parseTask.ContinueWith((Action<Task>)((task) =>
            {
                Application.Current.Dispatcher.BeginInvoke((Action)(() =>
                {
                    this.btnParse.IsEnabled = true;
                }));
            }));
        }

        private void ParseSingleFile(Subtitle subtitle)
        {
            try
            {
                if (!File.Exists(subtitle.Filename))
                {
                    this.AppendMessage(string.Format("File is not valid. {0}", subtitle.Filename), Colors.Red);
                    return;
                }

                string content = File.ReadAllText(subtitle.Filename, Encoding.UTF8);
                List<FontUsage> fontUsage = new List<FontUsage>();

                // parse font in v4+ style.
                this.ParseFont(ref content, @"[Ss]tyle\W?:\W?.+?\W?\,\W?(.+?)\W?\,", ref fontUsage, true);

                // parse font in dialogue.
                this.ParseFont(ref content, @"\\fn(.*?)[\\\}]", ref fontUsage, false);

                this.AppendMessage(subtitle.Filename, Colors.YellowGreen);

                foreach (var entry in fontUsage)
                {
                    this.AppendFont(entry);
                }

                this.AppendMessage("-------------------------------------------", Colors.YellowGreen);
            }
            catch (Exception ex)
            {
                this.AppendMessage(ex.Message, Colors.DarkRed);
            }
        }

        private void ParseFont(ref string content, string regexp, ref List<FontUsage> list, bool isHeader)
        {
            Regex regex = new Regex(regexp, RegexOptions.Multiline | RegexOptions.IgnoreCase);

            var collection = regex.Matches(content, 0);

            foreach (Match match in collection)
            {
                string group = match.Groups[1].Value;

                string simplifiedName = group;
                string traditionalName = group;
                if (this.chineseCharacterhash.Contains(group))
                {
                    string storeValue = this.chineseCharacterhash[group].ToString();
                    string[] values = storeValue.Split('|');
                    if (values.Length == 2)
                    {
                        simplifiedName = values[0];
                        traditionalName = values[1];
                    }
                }
                else
                {
                    simplifiedName = Win32API.ToSimplified(group);
                    traditionalName = Win32API.ToTraditional(group);
                    this.chineseCharacterhash.Add(group, string.Format("{0}|{1}", simplifiedName, traditionalName));
                }

                if (list.Any(f => (f.FontName == group || f.FontName == simplifiedName || f.FontName == traditionalName)))
                {
                    list.First(f => (f.FontName == group || f.FontName == simplifiedName || f.FontName == traditionalName)).Increase();
                }
                else
                {
                    // need check it again because maybe user will use the same font with different name, for example: use "Microsoft YaHei" and use "微软雅黑" at the same time, the font name is different but actually its the same font.
                    if (list.Any(f => f.IsAvailable && f.Font.FamilyNames.Values.Any(fn => string.Compare(fn, group, true) == 0)))
                    {
                        list.First(f => f.IsAvailable && f.Font.FamilyNames.Values.Any(fn => string.Compare(fn, group, true) == 0)).Increase();
                    }

                    list.Add(new FontUsage(group, isHeader, this.chineseCharacterhash));
                }
            }
        }

        private void AppendFont(FontUsage font)
        {
            Application.Current.Dispatcher.Invoke((Action<FontUsage>)((fontUsage) =>
            {
                string message = null;
                if (!fontUsage.IsAvailable)
                {
                    message = string.Format(" {0}{1}: {2}", fontUsage.IsHeaderStyle ? V4FONTPREFIX : string.Empty, fontUsage.FontName, fontUsage.Count);
                }
                else
                {
                    StringBuilder sb = new StringBuilder();
                    if (fontUsage.IsHeaderStyle)
                    {
                        sb.Append(V4FONTPREFIX);
                    }

                    foreach (string fn in fontUsage.Font.FamilyNames.Values)
                    {
                        sb.AppendFormat("{0}, ", fn);
                    }

                    sb.Remove(sb.Length - 2, 2);

                    message = string.Format(" {0}: {1}", sb.ToString(), fontUsage.Count);
                }

                Span span = new Span(new Run(message));
                span.Foreground = new SolidColorBrush(Colors.LightSeaGreen);
                Paragraph p = new Paragraph();
                p.Inlines.Add(span);

                if (!fontUsage.IsAvailable)
                {
                    string avaibilityTip = " [Missing]";
                    Span availitySpan = new Span(new Run(avaibilityTip));
                    span.FontFamily = availitySpan.FontFamily = new FontFamily("SimSun");
                    availitySpan.Foreground = new SolidColorBrush(Colors.HotPink);
                    p.Inlines.Add(availitySpan);
                }
                else
                {
                    string avaibilityTip = " [Available]";
                    Span availitySpan = new Span(new Run(avaibilityTip));
                    span.FontFamily = availitySpan.FontFamily = fontUsage.Font;
                    availitySpan.Foreground = new SolidColorBrush(Colors.DarkGreen);
                    p.Inlines.Add(availitySpan);
                }

                this.AppendMessage(p);
            }), font);
        }

        private void AppendMessage(string message, Color color)
        {
            Application.Current.Dispatcher.Invoke((Action)(() =>
            {
                Paragraph p = new Paragraph(new Run(message));
                p.Foreground = new SolidColorBrush(color);
                this.AppendMessage(p);
            }));
        }

        private void AppendContactInfo()
        {
            Application.Current.Dispatcher.Invoke((Action)(() =>
            {
                Hyperlink link = new Hyperlink(new Run("git.oschina.net"));
                link.NavigateUri = new Uri("http://git.oschina.net/syler/SubtitleFontChecker/tree/master/build", UriKind.Absolute);
                link.RequestNavigate += (s, e) =>
                {
                    Process.Start(e.Uri.ToString());
                };

                Paragraph p = new Paragraph();
                p.Inlines.Add(new Run("visit "));
                p.Inlines.Add(link);
                p.Inlines.Add(new Run(" for updates."));
                p.Foreground = new SolidColorBrush(Colors.Tomato);

                this.AppendMessage(p);
            }));
        }

        private void AppendMessage(Paragraph para)
        {
            this.txtMessage.Document.Blocks.Add(para);
            this.txtMessage.ScrollToEnd();
        }

        private string GetKey(string key)
        {
            RegistryKey registrationKey = Registry.CurrentUser.OpenSubKey(@"SOFTWARE\Syler\SubTitleFontCollector");
            if (registrationKey != null)
            {
                object value = registrationKey.GetValue(key);
                if (value != null)
                {
                    return value.ToString();
                }
            }

            return string.Empty;
        }

        private void SetKey(string key, string value)
        {
            RegistryKey lm = Registry.CurrentUser;
            RegistryKey software = lm.OpenSubKey("SOFTWARE", true);
            RegistryKey product = software.CreateSubKey("Syler\\SubTitleFontCollector");
            product.SetValue(key, value);
            lm.Close();
        }

        private void ClearMessages(object sender, RoutedEventArgs e)
        {
            this.txtMessage.Document.Blocks.Clear();
        }

        private void ShowHelpMessage(object sender, RoutedEventArgs e)
        {
            this.AppendMessage("font with a * means this font is using in v4+ style, generally speaking, it's the main font.", Colors.DarkMagenta);

            StringBuilder sb = new StringBuilder();
            foreach (FontFamily f in this.AllFonts)
            {
                sb.Append("[");
                foreach (string name in f.FamilyNames.Values)
                {
                    sb.AppendFormat("{0}, ", name);
                }

                sb.Remove(sb.Length - 2, 2);
                sb.Append("],  ");
            }

            sb.Remove(sb.Length - 3, 3);

            this.AppendMessage(string.Format("All system fonts: {0}", sb), Colors.DarkMagenta);
        }

        private void DropFiles(object sender, DragEventArgs e)
        {
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
            {
                string[] files = (string[])e.Data.GetData(DataFormats.FileDrop);

                this.ParsingFiles(files);
            }
        }
    }
}
